#include "fescreenreader.h"
#include <QDir>
#include <QXmlStreamReader>
#include <QStringList>
#include <QRect>
#include <QPoint>
#include <QSize>
#include <QIcon>

static const char *ELEM_SCREEN = "screen";
static const char *ELEM_VBOXLAYOUT = "vbox_layout";
static const char *ELEM_HBOXLAYOUT = "hbox_layout";
static const char *ELEM_SCALELAYOUT = "scale_layout";
static const char *ELEM_WIDGET = "widget";
static const char *ELEM_PROPERTY = "property";
static const char *ELEM_SIGNALBIND = "signal_bind";
static const char *ELEM_SIGNALBINDING = "signal_binding";

class FeScreenReaderPrivate
{
public:
	FeScreenReaderPrivate(FeScreenReader *_p, QIODevice *_device)
	{
		p = _p;
		device = _device;
	}

private:
	void readDocument();
	void readScreen(FeDOMScreen &);
	void readVBoxLayout(FeDOMVBoxLayout &);
	void readHBoxLayout(FeDOMHBoxLayout &);
	void readScaleLayout(FeDOMScaleLayout &);
	void readLayout(FeDOMLayout &);
	void readWidget(FeDOMWidget &);
	void readProperty(FeDOMProperty &);
	void readSignalBinding(FeDOMSignalBinding &);
	void readSignalBind(FeDOMSignalBind &);
	void readVariant(const QString &, const QString &, QVariant &);
	void readSize(const QString &, QSize &);
	void readInt(const QString &, int &);

private:
	friend class FeScreenReader;
	FeScreenReader *p;

	QXmlStreamReader *xml;
	FeDOMDocument *doc;
	QIODevice *device;
	QString error_string;
	int error_line;
	QDir res_base_dir;
};


void FeScreenReaderPrivate::readDocument()
{
	if(xml->readNextStartElement())
	{
		if (xml->name() == ELEM_SCREEN)
		{
			FeDOMScreen screen = doc->createScreen();
			doc->setScreen(screen);
			readScreen(screen);
		}
		else
		{
			xml->raiseError("The file is not an Screen file.");
		}
	}
}

void FeScreenReaderPrivate::readScreen(FeDOMScreen &screen)
{
	FeDOMLayout layout;
	FeDOMSignalBinding binding;
	while (xml->readNextStartElement())
	{
		if (xml->name() == ELEM_VBOXLAYOUT)
		{
			if(!layout.isNull())
			{
				xml->raiseError("<screen> has multiple layouts");
				return;
			}
			FeDOMVBoxLayout l;
			readVBoxLayout(l);
			if(!xml->error())
			{
				layout = l;
				screen.setLayout(layout);
			}
			else
			{
				return;
			}
		}
		else if (xml->name() == ELEM_HBOXLAYOUT)
		{
			if(!layout.isNull())
			{
				xml->raiseError("<screen> has multiple layouts");
				return;
			}
			FeDOMHBoxLayout l;
			readHBoxLayout(l);
			if(!xml->error())
			{
				layout = l;
				screen.setLayout(layout);
			}
			else
			{
				return;
			}
		}
		else if (xml->name() == ELEM_SCALELAYOUT)
		{
			if(!layout.isNull())
			{
				xml->raiseError("<screen> has multiple layouts");
				return;
			}
			FeDOMScaleLayout l;
			readScaleLayout(l);
			if(!xml->error())
			{
				layout = l;
				screen.setLayout(layout);
			}
			else
			{
				return;
			}
		}
		else if (xml->name() == ELEM_SIGNALBINDING)
		{
			if(!binding.isNull())
			{
				xml->raiseError("<screen> has multiple <signal_binging> items");
				return;
			}
			FeDOMSignalBinding b;
			readSignalBinding(b);
			if(!xml->error())
			{
				binding = b;
				screen.setSignalBinding(binding);
			}
			else
			{
				return;
			}
		}
		else
		{
			xml->raiseError("Invalid screen item: " + xml->name().toString());
			return;
		}

		/*if(!xml->error() && layout.isNull())
		{
			xml->raiseError("Missing screen layout item");
		}*/
	}
}

void FeScreenReaderPrivate::readVBoxLayout(FeDOMVBoxLayout &layout)
{
	int margin = 0;
	int spacing = 0;

	QStringRef margin_attr = xml->attributes().value("margin");
	QStringRef spacing_attr = xml->attributes().value("spacing");	
	if(!margin_attr.isEmpty())
	{
		readInt(margin_attr.toString(), margin);
		if(xml->error())
		{
			return;
		}
	}

	if(!spacing_attr.isEmpty())
	{
		readInt(spacing_attr.toString(), spacing);
		if(xml->error())
		{
			return;
		}
	}
	layout = doc->createVBoxLayout(margin, spacing);
	readLayout(layout);
}

void FeScreenReaderPrivate::readHBoxLayout(FeDOMHBoxLayout &layout)
{
	int margin = 0;
	int spacing = 0;

	QStringRef margin_attr = xml->attributes().value("margin");
	QStringRef spacing_attr = xml->attributes().value("spacing");	
	if(!margin_attr.isEmpty())
	{
		readInt(margin_attr.toString(), margin);
		if(xml->error())
		{
			return;
		}
	}

	if(!spacing_attr.isEmpty())
	{
		readInt(spacing_attr.toString(), spacing);
		if(xml->error())
		{
			return;
		}
	}
	layout = doc->createHBoxLayout(margin, spacing);
	readLayout(layout);
}

void FeScreenReaderPrivate::readScaleLayout(FeDOMScaleLayout &layout)
{
	QStringRef size_attr = xml->attributes().value("size");
	QSize size;
	if(!size_attr.isEmpty())
	{
		readSize(size_attr.toString(), size);
		if(xml->error())
		{
			return;
		}
	}
	else
	{
		xml->raiseError("\"size\" attribute missing for scale_layout");
		return;
	}
	layout = doc->createScaleLayout(size);
	readLayout(layout);
}

void FeScreenReaderPrivate::readLayout(FeDOMLayout &layout)
{
	while (xml->readNextStartElement())
	{
		FeDOMLayoutItem item;
		if (xml->name() == ELEM_WIDGET)
		{
			FeDOMWidget widget;
			readWidget(widget);
			item = widget;
		}
		else if(xml->name() == ELEM_VBOXLAYOUT)
		{
			FeDOMVBoxLayout child_layout;
			readVBoxLayout(child_layout);
			item = child_layout;
		}
		else if(xml->name() == ELEM_HBOXLAYOUT)
		{
			FeDOMHBoxLayout child_layout;
			readHBoxLayout(child_layout);
			item = child_layout;
		}
		else if(xml->name() == ELEM_SCALELAYOUT)
		{
			FeDOMScaleLayout child_layout;
			readScaleLayout(child_layout);
			item = child_layout;
		}
		else
		{
			xml->raiseError("Unsupported layout item: " + xml->name().toString());
			return;
		}

		if(!xml->error())
		{
			Q_ASSERT(!item.isNull());
			layout.addItem(item);
		}
		else
		{
			return;
		}
	}
}

void FeScreenReaderPrivate::readWidget(FeDOMWidget &widget)
{
	QString name;
	QString type;
	QStringRef name_attr = xml->attributes().value("name");
	QStringRef type_attr = xml->attributes().value("type");

	if(!name_attr.isEmpty())
	{
		name = name_attr.toString();
	}
	else
	{
		xml->raiseError("\"name\" attribute missing for <widget>");
		return;
	}

	if(!type_attr.isEmpty())
	{
		type = type_attr.toString();
	}
	else
	{
		xml->raiseError("\"type\" attribute missing for <widget>");
		return;
	}

	widget = doc->createWidget(name, type);

	while(xml->readNextStartElement())
	{
		if(xml->name() == ELEM_PROPERTY)
		{
			FeDOMProperty widget_property;
			readProperty(widget_property);
			if(!xml->error())
			{
				widget.addProperty(widget_property);
			}
			else
			{
				return;
			}
		}
		else
		{
			xml->raiseError("Invalid element in <widget>");
			return;
		}
	}
}

void FeScreenReaderPrivate::readProperty(FeDOMProperty &widget_property)
{
	QString name;
	QString type;
	QStringRef name_attr = xml->attributes().value("name");
	QStringRef type_attr = xml->attributes().value("type");

	if(!name_attr.isEmpty())
	{
		name = name_attr.toString();
	}
	else
	{
		xml->raiseError("\"name\" attribute missing for <property>");
		return;
	}

	if(!type_attr.isEmpty())
	{
		type = type_attr.toString();
	}
	else
	{
		xml->raiseError("\"type\" attribute missing for <property>");
		return;
	}

	QString text = xml->readElementText();
	QVariant value;
	readVariant(type, text, value);
	if(!xml->error())
	{
		widget_property = doc->createProperty(name, value);	
	}
	else
	{
		return;
	}

}

void FeScreenReaderPrivate::readSignalBinding(FeDOMSignalBinding &binding)
{
	binding = doc->createSignalBinding();

	while (xml->readNextStartElement())
	{
		FeDOMSignalBind bind;
		if (xml->name() == ELEM_SIGNALBIND)
		{
			FeDOMSignalBind bb;
			readSignalBind(bb);
			if(!xml->error())
			{
				bind = bb;
			}
			else
			{
				return;
			}
		}
		else
		{
			xml->raiseError("Unsupported signal bind: " + xml->name().toString());
			return;
		}

		if(!xml->error())
		{
			Q_ASSERT(!bind.isNull());
			binding.add(bind);
		}
		else
		{
			return;
		}
	}
}

void FeScreenReaderPrivate::readSignalBind(FeDOMSignalBind &bind)
{
	FeActualDataId signal;
	QString widget;
	QString property;
	QStringRef signal_attr = xml->attributes().value("signal");
	QStringRef widget_attr = xml->attributes().value("widget");
	QStringRef property_attr = xml->attributes().value("property");

	if(!signal_attr.isEmpty())
	{
		QStringList fields = signal_attr.toString().remove(' ').remove('\t').split(',');
		if(fields.size() == 3)
		{
			bool ok1, ok2, ok3;
			int pr = fields.at(0).toInt(&ok1);
			int gr = fields.at(1).toInt(&ok2);
			int sig = fields.at(1).toInt(&ok3);

			if(ok1 && ok2 && ok3)
			{
				signal = FEACTUALDATA_MAKEID(pr, gr, sig);
			}
			else
			{
				xml->raiseError("Invalid signal: " + signal_attr.toString());
			}
		}
	}
	else
	{
		xml->raiseError("\"signal\" attribute missing for <signal_bind>");
		return;
	}

	if(!widget_attr.isEmpty())
	{
		widget = widget_attr.toString();
	}
	else
	{
		xml->raiseError("\"widget\" attribute missing for <signal_bind>");
		return;
	}

	if(!property_attr.isEmpty())
	{
		property = property_attr.toString();
	}
	else
	{
		xml->raiseError("\"property\" attribute missing for <signal_bind>");
		return;
	}

	xml->skipCurrentElement();

	if(!xml->error())
	{
		bind = doc->createSignalBind(signal, widget, property);	
	}
	else
	{
		return;
	}
}

void FeScreenReaderPrivate::readVariant(const QString &type, const QString &text, QVariant &value)
{
	if(type == "bool")
	{
		QString tmp = text;
		tmp.remove(' ').remove('\t').toLower();
		value = QVariant((tmp == "true") ? true: false);
	}
	else if(type == "int")
	{
		QString tmp = text;
		tmp.remove(' ').remove('\t');
		bool ok;
		value = QVariant(tmp.toInt(&ok));
		if(!ok)
		{
			xml->raiseError("Invalid integer value: " + text);
		}
	}
	else if(type == "double")
	{
		QString tmp = text;
		tmp.remove(' ').remove('\t');
		bool ok;
		value = QVariant(tmp.toDouble(&ok));
		if(!ok)
		{
			xml->raiseError("Invalid double value: " + text);
		}
	}
	else if(type == "QString")
	{
		value = QVariant(text);
	}
	else if(type == "QSize")
	{
		QString tmp = text;
		QStringList fields = tmp.remove(' ').remove('\t').split(',');
		if(fields.size() == 2)
		{
			bool ok1, ok2;
			int w = fields.at(0).toInt(&ok1);
			int h = fields.at(1).toInt(&ok2);

			if(ok1 && ok2)
			{
				value = QVariant(QSize(w, h));
			}
			else
			{
				xml->raiseError("Invalid size value: " + text);
			}
		}
		else
		{
			xml->raiseError("Invalid size value: " + text);
		}
	}
	else if(type == "QPoint")
	{
		QString tmp = text;
		QStringList fields = tmp.remove(' ').remove('\t').split(',');
		if(fields.size() == 2)
		{
			bool ok1, ok2;
			int x = fields.at(0).toInt(&ok1);
			int y = fields.at(1).toInt(&ok2);
			if(ok1 && ok2)
			{
				value = QVariant(QPoint(x, y));
			}
			else
			{
				xml->raiseError("Invalid point value: " + text);
			}
		}
		else
		{
			xml->raiseError("Invalid point value: " + text);
		}
	}
	else if(type == "QRect")
	{
		QString tmp = text;
		QStringList fields = tmp.remove(' ').remove('\t').split(',');
		if(fields.size() == 4)
		{
			bool ok1, ok2, ok3, ok4;
			int x = fields.at(0).toInt(&ok1);
			int y = fields.at(1).toInt(&ok2);
			int w = fields.at(2).toInt(&ok3);
			int h = fields.at(3).toInt(&ok4);

			if(ok1 && ok2 && ok3 && ok4)
			{
				value = QVariant(QRect(x, y, w, h));
			}
			else
			{
				xml->raiseError("Invalid rect value: " + text);
			}
		}
		else
		{
			xml->raiseError("Invalid rect value: " + text);
		}
	}
	else if(type == "QIcon")
	{
		QPixmap p;
		if(!p.load(res_base_dir.absoluteFilePath(text)))
		{
			QByteArray path = res_base_dir.absoluteFilePath(text).toLocal8Bit();
			qDebug("Icon load error: %s", path.constData());
		}
		else
		{
			value = QVariant(QIcon(p));
		}
	}
	else
	{
		value = QVariant();
		xml->raiseError("Unsupported QVariant type: " + type);
	}
}

void FeScreenReaderPrivate::readSize(const QString &text, QSize &value)
{
	QString tmp = text;
	QStringList fields = tmp.remove(' ').remove('\t').split(',');
	bool ok1, ok2;
	int w = fields.at(0).toInt(&ok1);
	int h = fields.at(1).toInt(&ok2);

	if(fields.size() == 2 && ok1 && ok2)
	{
		value = QSize(w, h);
	}
	else
	{
		value = QSize();
		xml->raiseError("Invalid size value: " + text);
	}
}

void FeScreenReaderPrivate::readInt(const QString &text, int &value)
{
	QString tmp = text;
	bool ok;
	value = tmp.remove(' ').remove('\t').toInt(&ok);
	if(!ok)
	{
		xml->raiseError("Invalid integer value: " + text);
	}
}

// FeScreenReader
FeScreenReader::FeScreenReader(QIODevice *device)
{
	d = new FeScreenReaderPrivate(this, device);
	d->xml = 0;
	d->doc = 0;
	d->error_string = QString();
	d->error_line = 0;
	d->res_base_dir = QDir();
}

FeScreenReader::~FeScreenReader()
{
	delete d;
}

QDir FeScreenReader::resourceBaseDir() const
{
	return d->res_base_dir;
}

void FeScreenReader::setResourceBaseDir(const QDir &res_base_dir)
{
	d->res_base_dir = res_base_dir;
}

QString FeScreenReader::errorString() const
{
	return d->error_string;
}

int FeScreenReader::errorLine() const
{
	return d->error_line;
}

bool FeScreenReader::read(FeDOMDocument &doc)
{
	QXmlStreamReader xml(d->device);
	d->xml = &xml;
	d->doc = &doc;
	d->error_string = QString();
	d->error_line = 0;
	d->readDocument();
	if(xml.error())
	{
		d->error_string = xml.errorString();
		d->error_line = xml.lineNumber();
	}

	d->xml = 0;
	d->doc = 0;
	return !xml.error();
}